package kevin.hbase.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

@Slf4j
public final class HttpClientPoolUtil {
    private final static int CONNECTION_TIMEOUT = 15000;
    private final static int CONNECTION_MAX_NUM = 200;
    private final static int MAX_PERROUT = 40;
    private final static int MAX_ROUTe = 100;
    private static CloseableHttpClient httpClient = null;
    private final static Object syncLock = new Object();
    private static final String CHARSET = "utf-8";

    private static CloseableHttpClient getHttpClient(String url) {
        if (httpClient == null) {
            synchronized (syncLock) {
                if (httpClient == null) {
                    httpClient = createHttpClient(CONNECTION_MAX_NUM, MAX_PERROUT, MAX_ROUTe, url);
                }
            }
        }
        return httpClient;
    }

    private static CloseableHttpClient createHttpClient(int maxTotal, int maxPerRoute, int maxRoute, String url) {
        ConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory();
        LayeredConnectionSocketFactory sslsf = SSLConnectionSocketFactory.getSocketFactory();
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", plainsf)
                .register("https", sslsf).build();
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
        cm.setMaxTotal(maxTotal);
        cm.setDefaultMaxPerRoute(maxPerRoute);
        URL tranUrl = null;
        try {
            tranUrl = new URL(url);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }

        HttpHost httpHost = new HttpHost(tranUrl.getHost(), tranUrl.getPort() != -1 ? tranUrl.getPort() : 80);
        cm.setMaxPerRoute(new HttpRoute(httpHost), maxRoute);
        HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {
            @Override
            public boolean retryRequest(IOException exception,
                                        int executionCount, HttpContext context) {
                if (executionCount >= 5) {
                    return false;
                }
                if (exception instanceof NoHttpResponseException) {
                    return true;
                }
                if (exception instanceof SSLHandshakeException) {
                    return false;
                }
                if (exception instanceof InterruptedIOException) {
                    return false;
                }
                if (exception instanceof UnknownHostException) {
                    return false;
                }
                if (exception instanceof ConnectTimeoutException) {
                    return false;
                }
                if (exception instanceof SSLException) {
                    return false;
                }

                HttpClientContext clientContext = HttpClientContext.adapt(context);
                HttpRequest request = clientContext.getRequest();
                if (!(request instanceof HttpEntityEnclosingRequest)) {
                    return true;
                }
                return false;
            }
        };
        CloseableHttpClient httpClient = HttpClients.custom()
                .setConnectionManager(cm)
                .setRetryHandler(httpRequestRetryHandler)
                .build();
        return httpClient;
    }

    private static void config(HttpRequestBase httpRequestBase, String contentType, Integer contLen) {
        httpRequestBase.setHeader("Content-Type", contentType);
        if (contLen != null) {
            httpRequestBase.setHeader("Content-Length", contLen.intValue() + "");
        }
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectionRequestTimeout(CONNECTION_TIMEOUT)
                .setConnectTimeout(CONNECTION_TIMEOUT)
                .setSocketTimeout(CONNECTION_TIMEOUT).build();
        httpRequestBase.setConfig(requestConfig);
    }

    public static String doGet(String url, String contentType, String data) throws Exception {
        getHttpClient(url);
        String newUrl = url;
        if (data != null) {
            if (url.endsWith("?")) {
                newUrl = url + "&" + data;
            } else {
                newUrl = url + "?" + data;
            }
        }
        HttpGet httpget = new HttpGet(newUrl);
        config(httpget, contentType, data == null ? null : data.length());

        return request(httpget);
    }

    public static String doPost(String url, String contentType, String json)
            throws Exception {
        getHttpClient(url);
        HttpPost httppost = new HttpPost(url);
        config(httppost, contentType, null);
        httppost.setEntity(new StringEntity(json, Charset.forName(CHARSET)));
        return request(httppost);
    }

    public static String doPost(String url, String contentType, Map<String, Object> params) throws Exception {
        getHttpClient(url);
        HttpPost httppost = new HttpPost(url);
        config(httppost, contentType, null);
        setPostParams(httppost, params);
        return request(httppost);
    }

    private static String request(HttpRequestBase requestBase) throws Exception {
        CloseableHttpResponse response = null;
        try {
            response = httpClient.execute(requestBase, HttpClientContext.create());
            HttpEntity entity = response.getEntity();
            String result = EntityUtils.toString(entity, "utf-8");
            EntityUtils.consume(entity);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        } finally {
            try {
                if (response != null) {
                    response.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static void setPostParams(HttpPost httpost, Map<String, Object> params) {
        List<NameValuePair> nvps = new ArrayList<NameValuePair>();
        Set<String> keySet = params.keySet();
        for (String key : keySet) {
            nvps.add(new BasicNameValuePair(key, params.get(key).toString()));
        }
        try {
            httpost.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

}
